/*
* 文件：UnityLoop.cs
* 作者：王阳
* 时间：2019-5-20
* 描述：UnityLoop
*/

//#define ENABLED

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.LowLevel;

namespace UnityX
{
#if ENABLED == false
    [System.Obsolete("UnityLoop未启用，请从UnityX窗口勾选：暴露Unity内核底层循环系统(UnityLoop)", false)]
#endif
    public static class UnityLoop
    {
#if ENABLED && UNITY_EDITOR
        [UnityEditor.InitializeOnLoadMethod]
        private static void ClearCustomLoop()
        {
            UnityEditor.EditorApplication.playModeStateChanged += PlayModeStateChanged;
        }
        private static void PlayModeStateChanged(UnityEditor.PlayModeStateChange playModeStateChange)
        {
            if (playModeStateChange == UnityEditor.PlayModeStateChange.ExitingPlayMode)
            {
                PlayerLoop.SetPlayerLoop(PlayerLoop.GetDefaultPlayerLoop());
            }
        }
#endif

        /// <summary>
        /// Update之前调用
        /// </summary>
        public static event Action updatePre;
        private static void UpdatePreInternal()
        {
            updatePre?.Invoke();
        }
        /// <summary>
        /// Update调用
        /// </summary>
        public static event Action update;
        private static void UpdateInternal()
        {
            update?.Invoke();
            UpdateCoroutineInternal();
        }
        /// <summary>
        /// Update之后调用
        /// </summary>
        public static event Action updatePost;
        private static void UpdatePostInternal()
        {
            updatePost?.Invoke();
        }
        /// <summary>
        /// LateUpdate之前调用
        /// </summary>
        public static event Action lateUpdatePre;
        private static void LateUpdatePreInternal()
        {
            lateUpdatePre?.Invoke();
        }
        /// <summary>
        /// LateUpdate调用
        /// </summary>
        public static event Action lateUpdate;
        private static void LateUpdateInternal()
        {
            lateUpdate?.Invoke();
        }
        /// <summary>
        /// LateUpdate之后调用
        /// </summary>
        public static event Action lateUpdatePost;
        private static void LateUpdatePostInternal()
        {
            lateUpdatePost?.Invoke();
        }
        /// <summary>
        /// 帧开始之前调用
        /// </summary>
        public static event Action frameUpdatePre;
        private static void FrameUpdatePreInternal()
        {
            frameUpdatePre?.Invoke();
        }
        /// <summary>
        /// 帧结束之后调用
        /// </summary>
        public static event Action frameUpdatePost;
        private static void FrameUpdatePostInternal()
        {
            frameUpdatePost?.Invoke();
        }
        private static Coroutine[] coroutines = new Coroutine[256];
        private static int coroutineCount = 0;
        //更新协程
        private static void UpdateCoroutineInternal()
        {
            for (int i = 0; i < coroutineCount; i++)
            {
                try
                {
                    if (coroutines[i].Complete())
                    {
                        coroutines[i].enumerator = null;
                        coroutines[i] = coroutines[--coroutineCount];
                    }
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                    coroutines[i].enumerator = null;
                    coroutines[i] = coroutines[--coroutineCount];
                }
            }
        }
        /// <summary>
        /// 开始一个系统协程
        /// </summary>
        /// <param name="enumerator"></param>
        /// <returns></returns>
        public static Coroutine StartCoroutine(IEnumerator enumerator)
        {
            if (coroutineCount < coroutines.Length)
            {
                Coroutine coroutine = new Coroutine(enumerator);
                coroutines[coroutineCount++] = coroutine;
                return coroutine;
            }
            else
            {
                Debug.LogError("协程数量超出最大" + coroutines.Length + "个");
                return null;
            }
        }
        /// <summary>
        /// 停止一个系统协程
        /// </summary>
        /// <param name="coroutine"></param>
        public static void StopCoroutine(Coroutine coroutine)
        {
            for (int i = 0; i < coroutineCount; i++)
            {
                if (coroutines[i] == coroutine)
                {
                    coroutines[i].enumerator = null;
                    coroutines[i] = coroutines[--coroutineCount];
                    break;
                }
            }
        }
#if ENABLED
        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
        private static void Register()
        {
            PlayerLoopSystem root = PlayerLoop.GetCurrentPlayerLoop();
            for (int i = 0; i < root.subSystemList.Length; i++)
            {
                if (root.subSystemList[i].type.Name == "Update")
                {
                    root.subSystemList[i] = InsertLoop(root.subSystemList[i], new PlayerLoopSystem { type = typeof(UnityLoop), updateDelegate = UpdatePreInternal }, "ScriptRunBehaviourUpdate", true);
                    root.subSystemList[i] = InsertLoop(root.subSystemList[i], new PlayerLoopSystem { type = typeof(UnityLoop), updateDelegate = UpdatePostInternal }, "ScriptRunBehaviourUpdate", false);
                    for (int j = 0; j < root.subSystemList[i].subSystemList.Length; j++)
                    {
                        if (root.subSystemList[i].subSystemList[j].type.Name == "ScriptRunBehaviourUpdate")
                        {
                            root.subSystemList[i].subSystemList[j] = InsertLoop(root.subSystemList[i].subSystemList[j], new PlayerLoopSystem { type = typeof(UnityLoop), updateDelegate = UpdateInternal });
                        }
                    }
                }
                else if (root.subSystemList[i].type.Name == "PreLateUpdate")
                {
                    root.subSystemList[i] = InsertLoop(root.subSystemList[i], new PlayerLoopSystem { type = typeof(UnityLoop), updateDelegate = LateUpdatePreInternal }, "ScriptRunBehaviourLateUpdate", true);
                    root.subSystemList[i] = InsertLoop(root.subSystemList[i], new PlayerLoopSystem { type = typeof(UnityLoop), updateDelegate = LateUpdatePostInternal }, "ScriptRunBehaviourLateUpdate", false);
                    for (int j = 0; j < root.subSystemList[i].subSystemList.Length; j++)
                    {
                        if (root.subSystemList[i].subSystemList[j].type.Name == "ScriptRunBehaviourLateUpdate")
                        {
                            root.subSystemList[i].subSystemList[j] = InsertLoop(root.subSystemList[i].subSystemList[j], new PlayerLoopSystem { type = typeof(UnityLoop), updateDelegate = LateUpdateInternal });
                        }
                    }
                }
                else if (root.subSystemList[i].type.Name == "Initialization")
                {
                    root.subSystemList[i] = InsertLoop(root.subSystemList[i], new PlayerLoopSystem { type = typeof(UnityLoop), updateDelegate = FrameUpdatePreInternal }, null, true);
                }
                else if (root.subSystemList[i].type.Name == "PostLateUpdate")
                {
                    root.subSystemList[i] = InsertLoop(root.subSystemList[i], new PlayerLoopSystem { type = typeof(UnityLoop), updateDelegate = FrameUpdatePostInternal });
                }
            }
            PlayerLoop.SetPlayerLoop(root);

            //PrintAllPlayerLoop();
        }
#endif
        private static PlayerLoopSystem InsertLoop(PlayerLoopSystem loopSystem, PlayerLoopSystem insertLoopSystem, string name = null, bool qh = false)
        {
            if (loopSystem.subSystemList == null || loopSystem.subSystemList.Length == 0)
            {
                loopSystem.subSystemList = new PlayerLoopSystem[] { insertLoopSystem };
                return loopSystem;
            }
            PlayerLoopSystem[] plss = new PlayerLoopSystem[loopSystem.subSystemList.Length + 1];
            int insertIndex = qh ? 0 : loopSystem.subSystemList.Length;
            for (int i = 0; i < loopSystem.subSystemList.Length; i++)
            {
                if (loopSystem.subSystemList[i].type.Name == name)
                {
                    insertIndex = qh ? i : i + 1;
                    break;
                }
            }
            for (int i = 0; i < insertIndex; i++)
            {
                plss[i] = loopSystem.subSystemList[i];
            }
            plss[insertIndex] = insertLoopSystem;
            for (int i = insertIndex; i < loopSystem.subSystemList.Length; i++)
            {
                plss[i + 1] = loopSystem.subSystemList[i];
            }
            loopSystem.subSystemList = plss;
            return loopSystem;
        }
        //public static void PrintAllPlayerLoop()
        //{
        //    PlayerLoopSystem root = PlayerLoop.GetCurrentPlayerLoop();
        //    foreach (var item in root.subSystemList)
        //    {
        //        Debug.Log(item.type.Name);
        //        if (item.subSystemList != null)
        //        {
        //            foreach (var item2 in item.subSystemList)
        //            {
        //                Debug.Log(item.type.Name + "/" + item2.type.Name);
        //                if (item2.subSystemList != null)
        //                {
        //                    foreach (var item3 in item2.subSystemList)
        //                    {
        //                        Debug.Log(item.type.Name + "/" + item2.type.Name + "/" + item3.type.Name);
        //                        if (item3.subSystemList != null)
        //                        {
        //                            foreach (var item4 in item3.subSystemList)
        //                            {
        //                                Debug.Log(item.type.Name + "/" + item2.type.Name + "/" + item3.type.Name + "/" + item4.type.Name);
        //                            }
        //                        }
        //                    }
        //                }
        //            }
        //        }
        //    }
        //}
    }
    public class Coroutine
    {
        internal IEnumerator enumerator;
        private int type;
        private float time;
        private AsyncOperation asyncOperation;
        private Coroutine coroutine;
        internal Coroutine(IEnumerator enumerator)
        {
            this.enumerator = enumerator;
            Set();
        }
        internal bool Complete()
        {
            switch (type)
            {
                case 0:
                    return Complete2();
                case 1:
                    if (Time.time >= time)
                    {
                        return Complete2();
                    }
                    return false;
                case 2:
                    if (asyncOperation.isDone)
                    {
                        return Complete2();
                    }
                    return false;
                case 3:
                    if (coroutine.Complete())
                    {
                        return Complete2();
                    }
                    return false;
                default:
                    return true;
            }
        }
        private bool Complete2()
        {
            if (enumerator.MoveNext())
            {
                Set();
                return false;
            }
            else
            {
                return true;
            }
        }
        private void Set()
        {
            if (enumerator.Current == null)
            {
                type = 0;
                time = 0;
                return;
            }
            if (enumerator.Current is float)
            {
                type = 1;
                time = Time.time + (float)enumerator.Current;
                return;
            }
            if (enumerator.Current is int)
            {
                type = 1;
                time = Time.time + (int)enumerator.Current;
                return;
            }
            if (enumerator.Current is AsyncOperation)
            {
                type = 2;
                asyncOperation = (AsyncOperation)enumerator.Current;
                return;
            }
            if (enumerator.Current is IEnumerator)
            {
                type = 3;
                coroutine = new Coroutine((IEnumerator)enumerator.Current);
                return;
            }
            throw new Exception("[yield return类型错误：" + enumerator.Current.GetType().Name + "] [只支持类型：float、int、null、IEnumerator、AsyncOperation]");
        }
    }
}